/* Copyright (C) 2007-2010 STS Soft

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using STSdb.General.Buffers;
using STSdb.General.Collections;
using STSdb.General.Compression;
using STSdb.Files;
using System.Diagnostics;

namespace STSdb.Data
{
    public partial class FTable<TKey, TRecord>
    {
        private class CollapsedCollection : BaseCollection
        {
            public IRecordCollection Items;

            public CollapsedCollection(Link parent)
                : base(parent)
            {
                Items = new DictionaryRecordCollection(0);
            }

            public override TRecord this[byte[] index]
            {
                get
                {
                    TRecord record;
                    if (!TryGet(index, out record))
                        throw new KeyNotFoundException("index");

                    return record;
                }
                set
                {
                    BinaryBuffer key = new BinaryBuffer(index.Left(index.Length - (int)Level));
                    int count = Items.Count;
                    Items[key] = value;
                    if (!Owner.BufferManager.Split && Items.Count > count)
                    {
                        Owner.BufferManager.Count++;
                        int fromIndex = index.Length - 1 - (int)Level;
                        if (Items.Count > Owner.BufferManager.BlockCapacity && fromIndex > 0)
                        {
                            Owner.BufferManager.Split = true;
                            //Split
                            ExpandedCollection collection = new ExpandedCollection(Owner);
                            Owner.BufferManager.DataSize -= Owner.Wrapper.Ptr.Size;
                            Owner.Wrapper.Ptr = Ptr.NULL;
                            Owner.Collection = collection;

                            byte[] buffer = new byte[index.Length];
                            Buffer.BlockCopy(index, fromIndex, buffer, fromIndex, index.Length - fromIndex);
                            foreach (var kv in Items)
                            {
                                kv.Key.Raw.CopyTo(buffer, 0);
                                collection[buffer] = kv.Value;
                            }

                            Owner.BufferManager.Split = false;
                        }
                    }

                    Owner.IsModified = true;
                    Owner.BufferManager.IsModified = true;
                }
            }

            public override bool TryGet(byte[] index, out TRecord record)
            {
                BinaryBuffer key = new BinaryBuffer(index.Left(index.Length - (int)Level));

                return Items.TryGetValue(key, out record);
            }

            public override void Delete(byte[] index)
            {
                BinaryBuffer key = new BinaryBuffer(index.Left(index.Length - (int)Level));

                if (Items.Remove(key))
                {
                    Owner.BufferManager.Count--;

                    Owner.IsModified = true;
                    Owner.BufferManager.IsModified = true;
                }
            }

            public override void Modify(byte[] index)
            {
                BinaryBuffer key = new BinaryBuffer(index.Left(index.Length - (int)Level));

                TRecord record;
                if (Items.TryGetValue(key, out record))
                {
                    Owner.IsModified = true;
                    Owner.BufferManager.IsModified = true;
                }
            }

            public override IEnumerable<Row<byte[], TRecord>> Forward(byte[] firstIndex, byte[] lastIndex)
            {
                BinaryBuffer key = new BinaryBuffer(firstIndex.Left(firstIndex.Length - (int)Level));

                if (!(Items is SortedListRecordCollection))
                    Items = new SortedListRecordCollection(Items);

                //Items.Sort(); is done implicitly in Items.Search(key)

                int idx = Items.Search(key);
                if (idx < 0)
                    idx = ~idx;

                for (; idx < Items.Count; idx++)
                {
                    KeyValuePair<BinaryBuffer, TRecord> kv = Items[idx];
                    Row<byte[], TRecord> row = new Row<byte[], TRecord>(new byte[firstIndex.Length], kv.Value);
                    kv.Key.Raw.CopyTo(row.Key, 0);

                    yield return row;
                }
            }

            public override IEnumerable<Row<byte[], TRecord>> Backward(byte[] lastIndex, byte[] firstIndex)
            {
                BinaryBuffer key = new BinaryBuffer(lastIndex.Left(lastIndex.Length - (int)Level));

                if (!(Items is SortedListRecordCollection))
                    Items = new SortedListRecordCollection(Items);

                //Items.Sort(); is done implicitly in Items.Search(key)

                int idx = Items.Search(key);
                if (idx < 0)
                    idx = ~idx - 1;

                for (; idx >= 0; idx--)
                {
                    KeyValuePair<BinaryBuffer, TRecord> kv = Items[idx];
                    Row<byte[], TRecord> row = new Row<byte[], TRecord>(new byte[lastIndex.Length], kv.Value);
                    kv.Key.Raw.CopyTo(row.Key, 0);

                    yield return row;
                }
            }

            public override IEnumerator<Row<byte[], TRecord>> GetEnumerator()
            {
                foreach(var kv in Items)
                {
                    Row<byte[], TRecord> row = new Row<byte[], TRecord>(new byte[Owner.BufferManager.Depth], kv.Value);
                    kv.Key.Raw.CopyTo(row.Key, 0);

                    yield return row;
                }
            }

            public override void Save(MemoryStream stream)
            {
                if (Items.Count == 0)
                    return;

                BinaryWriter writer = new BinaryWriter(stream);

                CountCompression.Serialize(writer, Items.Count);
                CountCompression.Serialize(writer, Items.First().Key.Raw.Length);

                int i = 0;
                TRecord[] records = new TRecord[Items.Count];
                foreach (var kv in Items)
                {
                    writer.Write(kv.Key.Raw);
                    records[i++] = kv.Value;
                }

                IIndexer<TRecord> indexer = new CollapsedCollection.InternalIndexer(records);
                Owner.BufferManager.RecordPersist.Store(writer, indexer);
            }

            public override void Load(MemoryStream stream)
            {
                BinaryReader reader = new BinaryReader(stream);

                int count = (int)CountCompression.Deserialize(reader);
                int length = (int)CountCompression.Deserialize(reader);

                BinaryBuffer[] keys = new BinaryBuffer[count];
                for (int i = 0; i < count; i++)
                    keys[i] = new BinaryBuffer(reader.ReadBytes(length));

                TRecord[] records = new TRecord[count];

                IIndexer<TRecord> indexer = new CollapsedCollection.InternalIndexer(records);
                Owner.BufferManager.RecordPersist.Load(reader, ref indexer);

                switch (Owner.BufferManager.Mode)
                {
                    case FTable<TKey, TRecord>.AccessMode.Direct:
                        Items = new DictionaryRecordCollection(keys, indexer);
                        break;
                    case FTable<TKey, TRecord>.AccessMode.Sequential:
                        Items = new SortedListRecordCollection(keys, indexer);
                        break;
                }
            }

            private class InternalIndexer : IIndexer<TRecord>
            {
                public readonly TRecord[] Records;

                public InternalIndexer(TRecord[] records)
                {
                    Records = records;
                }

                #region IIndexer<TRecord> Members

                public TRecord this[int index]
                {
                    get
                    {
                        return Records[index];
                    }
                    set
                    {
                        Records[index] = value;
                    }
                }

                public int Count
                {
                    get { return Records.Length; }
                }

                #endregion
            }
        }
    }
}
